/**
 * Vui365 通用工具类库（适用于 CDN 方式）
 * @version 1.1.0-beta.220611
 * 包含：
 * EsConfig: 全局配置信息助手类
 * EsLoginState: 全局登录状态助手类
 * EsUtils: 全局通用工具类
 * getAxios: 获取标准化 Axios 实例对象
 * myAxios: 已经标准化的 axios 实例对象
 * 常用方法：
 * this.gpEsConfig.setResourceUrl() # 切换本地资源和远程资源
 * this.gpLoginState.isLogin() # 判断登录状态
 * this.gpLoginState.setToken(token) # 保存已登录的 token 信息
 */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
            (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Vui365 = factory());
})(this, (function () {
    'use strict';

    /**
     * 全局配置信息
     */
    class EsConfig {
        // 注意：ES6 明确规定，Class 内部只有静态方法，没有静态属性。所以要把静态属性改为静态方法。ES7 才支持静态属性，可用 babel 编译。
        // 推广渠道标识
        channelName = "";
        // 应用市场标识 wxmp=微信小程序&app=应用APP&h5=H5应用&huawei=华为应用市场&xiaomi=小米应用市场&vivo=Vivo应用市场&oppo=Oppo应用市场
        appMarket = "h5";
        // 登录接口提供商 weixin=App微信登录&wxmp=微信小程序登录&mobile=手机号登录&test=模拟测试登录
        loginProvider = "mobile";
        // 是否微信小程序环境
        isEnvOfWeixinMP = false;
        // 是否 APP 环境（HTML5 Plus）
        isEnvOfPlus = false;
        // 是否 H5 环境
        isEnvOfH5 = true;
        // 是否显示电池占位区（微信小程序不用，APP和H5需要）
        showTopStatusBar = true;
        // 登录会话缓存名称
        tokenName = "admin_token";
        // 环境模式(debug=调试环境&dev=开发环境&prod=生产环境)
        envMode = 'dev'
        // 开发服务器域名（用于在线图片）
        domain = "http://abc.lh/";
        // domain = "http://tvadmin.lh/";
        // 开发服务器接口地址
        apiBaseUrl = "http://abc.lh/index.php";
        // apiBaseUrl = "http://tvadmin.lh/index.php";
        // 默认头像图标
        defaultAvatarIcon = "/static/images/default.png";
        // 支付宝支付名称 alipay=支付宝App支付&alipayh5=支付宝H5支付
        payNameOfAlipay = "alipayh5";
        // 微信支付名称 wxpay=微信App支付&wxpaymp=微信小程序支付&wxpayh5=微信H5支付
        payNameOfWxpay = "wxpayh5";
        // 支付间隔时间
        payInterval = 10;
        // 短信发送间隔时间（秒）
        smsInterval = 30;
        // 短信测试验证码（超级验证码）
        smsTestingCaptcha = "0609";
        // 是否使用模拟数据（Mock.js）
        useMockData = false;
        // http请求超时时间（毫秒）
        httpTimeout = 60000;

        constructor(option) {
            if (!option || typeof (option) != 'object') {
                return;
            }
            for (let k in option) {
                if (this[k]) {
                    this[k] = option[k];
                }
            }
        }

        // 静态实例对象
        static instance = null;
        static me(option = null, init = false) {
            if (!EsConfig.instance || init) {
                EsConfig.instance = new EsConfig(option);
            }
            return EsConfig.instance;
        }

        /**
         * 设置资源URL地址（切换本地路径为远程路径）
         * @example setResourceUrl('/uploads/2021/1119/d3974335bb7fc1ebe065f3c5c0dfa84f.png')
         * @example setResourceUrl('/static/h5app/images/icon.png')
         */
        setResourceUrl(path) {
            if (!path) {
                return '';
            }
            if (path.indexOf("/static/" == 0)) {
                // return path;
            }
            return path.indexOf('http') == 0 ? path : (this.domain + path);
        }

        /**
         * 返回可用的头像地址
         */
        getAvatarImage(path) {
            if (!path || path.trim().length == 0) {
                return this.defaultAvatarIcon;
            }
            return this.setResourceUrl(path);
        }

        /**
         * 解析 Laravel 服务端的表单校验消息
         */
        parseValidateMessageOfLaravel(message) {
            if (typeof message == "object") {
                let messageArr = [];
                for (let k in message) {
                    message[k].forEach((text) => {
                        messageArr.push(text);
                    });
                }
                message = messageArr.join("<br />");
            }
            return message;
        }
    }

    /**
     * 用户登录信息公共类
     */
    class LoginState {
        tokenName = '';
        constructor(tokenName = '') {
            this.tokenName = tokenName || EsConfig.tokenName;
        }

        // 静态实例对象
        static instance = null;
        static me(tokenName = '', init = false) {
            if (!LoginState.instance || init) {
                LoginState.instance = new LoginState(tokenName);
            }
            return LoginState.instance;
        }

        /**
         * 保存登录会话信息
         */
        setToken(token) {
            window.sessionStorage.setItem(this.tokenName, token);
        }
        /**
         * 获取登录会话信息
         * 返回 string|null
         */
        getToken() {
            return window.sessionStorage.getItem(this.tokenName);
        }
        /**
         * 清除登录会话信息
         */
        clearToken() {
            window.sessionStorage.removeItem(this.tokenName);
        }
        /**
         * 判断登录状态
         */
        isLogin() {
            return this.getToken() ? true : false;
        }
    }

    /**
     * 通用工具类
     * @version 1.0.0
     * @method stdMessage(success, message, data = '', code = 0) 返回标准消息对象
     * @method random(min, max=0) 生成随机数
     */
    class EsUtils {

        /**
         * 返回标准消息对象
         * @param {Boolean} $success 是否成功
         * @param {String} message 消息内容
         * @param {Object} data 数据内容
         * @param {Number} code 状态码 200=成功,401=未授权(未登录),403=禁止访问,500=服务器错误
         * @see https://tool.lu/httpcode/
         */
        static stdMessage(success, message, data = '', code = 0) {
            return {
                success: success,
                message: message,
                data: data,
                code: code,
            }
        }

        /**
         * 生成随机数
         * @example random(1,10) 生成 1...10之间的随机数（包括1和10）。random(10) 生成 0...10 之间的随机数
         */
        static random(min, max = 0) {
            if (max == 0) {
                max = min;
                min = 0;
            }
            return Math.round(Math.random() * (max - min)) + min;
        }

        /**
         * 返回数组中一个或多个随机的键
         */
        static arrayRandom(array, number = 1) {
            let idx = Math.floor((Math.random() * array.length));
            let data = [], array2 = [...array];
            // splice() 方法会改变原始数组
            for (let i = 0; i < number; i++) {
                data.push(array2.splice(idx, 1)[0]);
            }
            return number == 1 ? data[0] : data;
        }

        /**
         * 将数组打乱
         * 注意：此方法会改变原始数组！
         */
        static arrayShuffle(array) {
            // sort() 方法会改变原始数组！
            array.sort(() => {
                return Math.random() > 0.5 ? -1 : 1;
            })
            return array;
        }

        static arrayCopy(arr, result) {
            result = result || [];
            for (var i = 0; i < arr.length; i++) {
                if (arr[i] instanceof Array) {
                    result[i] = [];
                    EsUtils.arrayCopy(arr[i], result[i]);
                } else {
                    result[i] = arr[i];
                }
            }
            return result;
        }
        /**
         * 缩减字符串
         */
        static shortText(text, length = 10, lastLength = 0, ellipsis = '...') {
            if (!text || text.length < length) {
                return text;
            }
            if (lastLength == 0) {
                return text.substring(0, length) + ellipsis;
            }
            return text.substring(0, length - lastLength) + ellipsis + text.substr(-lastLength);
        }
        /**
         * 字节单位转换
         */
        static bytesToSize(size) {
            if (bytes === 0) {
                return '0 B';
            }
            var k = 1024, // or 1024
                sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
                i = Math.floor(Math.log(bytes) / Math.log(k));

            return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];
        }
        /**
         * 数字单位
         */
        static numberUnit(num = 0) {
            if (!num) {
                return 0;
            }
            num = parseInt(num);
            if (num > 10000) {
                return Math.round(num / 1000) + '万';
            }
            return num;
        }
        /**
         * 数值限制
         */
        static numberLimit(value, min = 0, max = 10) {
            if (isNaN(value)) {
                return min;
            }
            value = Number(value)
            if (value < min) {
                return min;
            }
            if (value > max) {
                return max;
            }
            return value;
        }
        // 对Date的扩展，将 Date 转化为指定格式的String 
        // 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符， 
        // 年(y)可以用 1-4 个占位符，毫秒(S)只能用 1 个占位符(是 1-3 位的数字) 
        // 例子： 
        // (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423 
        // (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18 
        static timeFormat(fmt, text = null) {
            let date = text ? new Date(text) : new Date();
            //author: meizz 
            let o = {
                "M+": date.getMonth() + 1, //月份 
                "d+": date.getDate(), //日 
                "h+": date.getHours(), //小时 
                "m+": date.getMinutes(), //分 
                "s+": date.getSeconds(), //秒 
                "q+": Math.floor((date.getMonth() + 3) / 3), //季度 
                "S": date.getMilliseconds() //毫秒 
            };
            if (/(y+)/.test(fmt)) {
                fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
            }
            for (let k in o)
                if (new RegExp("(" + k + ")").test(fmt)) {
                    fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k])
                        .length)));
                }
            return fmt;
        }
        // 生成浏览器请求参数
        static buildQueryString(obj) {
            let usp = new URLSearchParams();
            for (let k in obj) {
                usp.set(k, typeof (obj[k]) == 'object' ? JSON.stringify(obj[k]) : obj[k]);
            }
            return usp.toString();
        }
        // 获取浏览器请求参数
        static getQueryString(name = '', defaultValue = null, queryString = '') {
            if (queryString.length == 0) {
                queryString = window.location.search;
                if (queryString.length == 0) {
                    return name.length == 0 ? {} : defaultValue;
                }
            }
            let usp = new URLSearchParams(queryString);
            if (name.length > 0) {
                return usp.get(name) || defaultValue;
            }
            let obj = {};
            usp.forEach((v, k) => {
                obj[k] = v;
            })
            return obj;
        }
        // 获取cookie参数
        static getCookie(name, defval = null, cookie = '') {
            if (!cookie) {
                cookie = document.cookie;
            }
            if (!cookie) {
                return defval;
            }
            // cookie1=value; cookie2=value; cookie3=value;
            var params = cookie.split(';'),
                pair = [];
            for (let param of params) {
                pair = param.split('=');
                if (pair[0].trim() == name) {
                    return pair[1];
                }
            }
            return defval;
        }
        /**
         * 复制到剪贴板
         */
        static copyText(text) {
            let input = document.createElement('input');
            input.value = text;
            document.body.appendChild(input);
            input.select();
            document.execCommand('copy');
            document.body.removeChild(input);
        }
    }


    /**
     * 创建自定义 Axios 实例
     * 是否启用 mock 数据（如果使用 mock 数据，则 url 不能拼接 BaseUrl，否则路径会匹配不上）
     * process.env.VUE_APP_API_BASE_URL // .env 文件主要用于多人协同的团队开发环境，我是一个人全栈，所以不使用 .env 文件，省得每次更改都要重新编译。。。
     */
    function getAxios(baseURL = '', timeout = 60000, fnGetToken = null) {
        const myAxios = axios.create({
            baseURL: baseURL,
            timeout: timeout,
        })

        // axios 添加请求拦截
        myAxios.interceptors.request.use(config => {
            if (typeof (fnGetToken) == 'function') {
                const token = fnGetToken();
                if (token && typeof (token) == "string") {
                    config.headers.Authorization = token;
                }
            }
            console.log('添加请求拦截', config.headers.Authorization);
            return config;
        })

        // axios 添加输出拦截
        myAxios.interceptors.response.use(response => {
            console.log('添加输出拦截', response);
            if (response.data.success == undefined) {
                return Promise.reject(EsUtils.stdMessage(false, "服务器返回的数据格式错误"))
            }
            return Promise.resolve(response.data);
        }, error => {
            return Promise.reject(EsUtils.stdMessage(false, error.message))
        })
        return myAxios;
    }

    // 获取标准化的 axios 示例（配置了拦截请求和输出请求）
    const myAxios = getAxios(EsConfig.apiBaseUrl, EsConfig.httpTimeout, () => { LoginState.me().getToken() });

    /**
     * 发起上传文件的请求并返回标准消息格式
     */
    async function stdUploadFile(files, url = '/api/admin/uploader', params = {}) {
        let formData = new FormData(),
            isMultiple = files.length > 1;
        formData.append("is_multiple", isMultiple ? 1 : 0);
        if (isMultiple) {
            for (let file of files) {
                if (typeof file == "object") {
                    formData.append("file[]", file);
                }
            }
        } else {
            formData.append("file", files[0]);
        }
        for (let k in params) {
            formData.set(k, params[k]);
        }
        const msgInfo = await myAxios.post(url, formData);
        console.log("[上传文件] msgInfo=", msgInfo);
        if (msgInfo.success && msgInfo.data instanceof Array) {
            let paths = [];
            msgInfo.data.forEach((row) => {
                paths.push(row.path);
            });
            msgInfo.data = paths.join(",")
        }
        return msgInfo;
    }

    /**
     * 发送短信验证码
     * @param {String} mobile
     * @param {String} event 事件类型，如：update_mobile
     */
    async function stdSendSMS(mobile, event, isDebug = 0, url = '/api/sms/send') {
        if (mobile.length != 11) {
            return EsUtils.stdMessage(false, '手机号码无效')
        }
        let data = {
            mobile: mobile,
            event: event,
            isDebug: isDebug,
        };
        const msgInfo = await myAxios(url, data, 'POST');
        console.log('[发送短信验证码] data=', data, ' msgInfo=', msgInfo)
        return msgInfo;
    }


    /**
     * Vue.use(vui365) 安装函数
     * see: [Vue.use()用法](https://www.cnblogs.com/xiao1314/p/14426569.html)
     */
    function install(Vue, option = {}) {
        //  注册组件
        // Vue.component("CdButton", getComponentOfCdButton())
        // [废弃] 全局属性使用 gp 前缀。gp = global prototype 的意思。
        // 全局属性使用 vui 前缀。vui = Vui365 的意思。
        Vue.prototype.vuiEsConfig = EsConfig.me(option);
        Vue.prototype.vuiEsUtils = EsUtils;
        Vue.prototype.vuiLoginState = LoginState.me(option.tokenName);
        Vue.prototype.vuiAjax = myAxios;
        Vue.prototype.vuiUploadFile = stdUploadFile;
        Vue.prototype.vuiUploadFile = stdSendSMS;
    }
    // ESM （ES6 模块导出）
    // export {
    //     install,
    //     EsConfig,
    //     EsUtils,
    //     LoginState,
    //     getAxios,
    //     myAxios,
    //     stdUploadFile,
    //     stdSendSMS,
    // }

    // UMD 模块导出
    return {
        install,
        EsConfig,
        EsUtils,
        LoginState,
        // getAxios,
        // myAxios,
        // stdUploadFile,
        // stdSendSMS,
    };
}));
